/*
 * Copyright 2010 Marcin Blazejewski
 *
 * This file is part of Nofate.
 *
 * Nofate is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Nofate is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Nofate. If not, see <http://www.gnu.org/licenses/>.
 *
 */


#include <config.h>
#include "genesis.hpp"

#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <iostream>

using namespace std;

float noise(float x, float y, F2 &src);
float interpolate(float y1, float y2, float x);


void pol2cart(float r, float a, float &x, float &y) {
    x = r * cos(a);
    y = r * sin(a);
}

void cart2pol(float x, float y, float &r, float &a) {
    r = sqrt( x*x + y*y );
    if (x > 0) {
        if (y >= 0) a = atan(y/x);
        else a = atan(y/x) + 2*M_PI;
    }
    else if (x < 0) {
        a = atan(y/x) + M_PI;
    }
    else { /* x == 0 */
        if (y > 0) a = M_PI / 2.0f;
        else if (y < 0) a = 3.0f/2.0f*M_PI;
        else /* y == 0 */ a = 0;
    }
}

int randv(int x0, int x1, int lead_percent, int tail_percent) {
    int full_size = x1 - x0;
    int lead = full_size * lead_percent / 100;
    int tail = full_size * tail_percent / 100;
    int span = full_size - lead - tail;
    if (span < 2) span = full_size;

    if (span > 1) return x0 + lead + (rand() % span);
    return x0 + lead;
}

F2::F2(unsigned int w, unsigned int h) {
    width = w;
    height = h;
    data = new float*[w];
    memset(data, 0, sizeof(float*) * w);
    for (int x = 0; x < w; x++) { 
        data[x] = new float[h];
        memset(data[x], 0, sizeof(float) * h);
        
    }
}

F2::F2(const F2 &r) {
    width = r.width;
    height = r.height;
    data = new float*[width];
    memset(data, 0, sizeof(float*) * width);
    for (int x = 0; x < width; x++) { 
        data[x] = new float[height];
        memcpy(data[x], r.data[x], sizeof(float) * height);
    }
}

F2::~F2() {
    for (int x = 0; x < width; x++)
        delete [] data[x];
    delete [] data;
}

void F2::fill(float v, int x0, int y0, int x1, int y1) {
    if (x0 < 0) x0 = 0;
    if (y0 < 0) y0 = 0;
    if (x1 >= width) x1 = width - 1;
    if (y1 >= height) y1 = height  - 1;

    for (int x = x0; x <= x1; x++) {
        for (int y = y0; y <= y1; y++) {
            data[x][y] = v;
        }
    } 
}

void F2::fill(float v) {
    fill(v, 0, 0, width-1, height-1);
}

float F2::avg() {
    return get_avg();
}

float F2::get_avg() {
    float sum = 0;
    for (int x = 0; x < width; x++)
        for (int y = 0; y < height; y++)
            sum += data[x][y];
    
    return sum / (width * height);
}

float F2::get_max(int *rx, int *ry) {
    float max = data[0][0];
    if (rx != NULL) *rx = 0;
    if (ry != NULL) *ry = 0;
    
    for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++) {
            if (data[x][y] > max) {
                max = data[x][y];
                if (rx != NULL) *rx = x;
                if (ry != NULL) *ry = y;
            }
        }
    }
    
    return max;
}

float F2::get_min(int *rx, int *ry) {
    float min = data[0][0];
    if (rx != NULL) *rx = 0;
    if (ry != NULL) *ry = 0;
    
    for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++) {
            if (data[x][y] < min) {
                min = data[x][y];
                if (rx != NULL) *rx = x;
                if (ry != NULL) *ry = y;
            }
        }
    }
    
    return min;
}

int F2::count(float v0, float range) {
    int count = 0;
    for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++) {
            if (fabs(data[x][y] - v0) <= range) count++;
        }    
    }

    return count;
}

void F2::normalize() {
    float a = get_max();

    for (int x = 0; x < width; x++)
        for (int y = 0; y < height; y++)
            data[x][y] /= a;
}

void F2::randomize(float v0, float v1, int m) {
    int range = (v1-v0) * m;
    for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++) {
            data[x][y] = v0 + (float)(rand() % range) / m;
        }
    }
}

void F2::blend(F2 &r, float ratio, int x0, int y0) {
    int minx = (x0 < 0) ? -x0 : 0;
    int miny = (y0 < 0) ? -y0 : 0;
    int maxx = ((x0+r.width) >= width) ? (width-x0) : r.width;
    int maxy = ((y0+r.height) >= height) ? (height-y0) : r.height;
    for (int x = minx; x < maxx; x++) {
        for (int y = miny; y < maxy; y++) {
            
            float v0 = data[x+x0][y+y0] * (1.0f - ratio);
            float v1 = r.data[x][y] * ratio;
            data[x+x0][y+y0] = v0 + v1;
        }
    }
}

/*
void F2::brush(int x0, int y0, float r, float v) {
    for (int x = -r; x <= r; x++) {
        for (int y = -r; y <= r; y++) {
            float d = sqrt( x*x + y*y );
            if (d == 0) d = 1;
            data[x0+x][y0+y] += (v / d);
        }
    }
}
*/


void F2::stamp(F2 &r, F2 &orig) {
    int x0 = 0;
    int y0 = 0;
    float R = 5;

    for (int x = 0; x < r.width; x++) {
        for (int y = 0; y < r.height; y++) {
            if (r.data[x][y] < 0) continue;


            for (int xb = -R; xb <= R; xb++) {
                for (int yb = -R; yb <= R; yb++) {

                    if ((x0+x+xb < 0) || (x0+x+xb >= width) ||
                        (y0+y+yb < 0) || (y0+y+yb >= height)) continue;

                    float d = sqrt( xb*xb + yb*yb );
                    float ratio = (3 - 0.5*d) / 6.0;
                    
                    //data[x0+x+xb][y0+y+yb] = (1.0f - ratio) * orig.data[x0+x+xb][y0+y+yb] + 
                    //    r.data[x][y] * ratio;

                    float a = r.data[x][y] - data[x0+x+xb][y0+y+yb];
                    data[x0+x+xb][y0+y+yb] += ratio * a;
                }
            }
            
        }
    }


    for (int x = 0; x < r.width; x++) {
        for (int y = 0; y < r.height; y++) {
            if (r.data[x][y] < 0) continue;
            data[x0+x][y0+y] = r.data[x][y];
        }
    }
}


void F2::level_step(F2 &mask, int x, int y, float range, float v0) {
    float v = data[x][y];
    
    if (mask.data[x][y] != 0) return;
    if (fabs(v0 - v) > range) return;

    mask.data[x][y] = 1;
    data[x][y] = v0;
    if (x > 0) level_step(mask, x-1, y, range, v0);
    if (y > 0) level_step(mask, x, y-1, range, v0);
    if (x < (width-1)) level_step(mask, x+1, y, range, v0);
    if (y < (height-1)) level_step(mask, x, y+1, range, v0);
}

void F2::level(int x0, int y0, float range) {
    F2 mask(width, height);
    mask.fill(0);

    level_step(mask, x0, y0, range, data[x0][y0]);
}

void F2::smooth(int x0, int y0, int x1, int y1) {
    if (x0 < 0) x0 = 0;
    if (y0 < 0) y0 = 0;
    if ((x1 <= x0) || (x1 >= width)) x1 = width-1;
    if ((y1 <= y0) || (y1 >= height))  y1 = height-1;
    int x, y;

    for (x=x0+1; x < x1; x++) {
        for (y=y0+1; y < y1; y++) {
            data[x][y] += data[x][y-1] + data[x][y+1] + data[x-1][y] + data[x+1][y];
            data[x][y] /= 5.0f;
        }
    }
}

int F2::find_maxhline(int y0, float v0, float v1, int &rx0, int &rx1) {
    int ret_l = 0;
    int x0 = -1;
    int l = 0;
    bool line = false;

    for (int x = 0; x < width; x++) {
        
        if (!line) {
            /* no current line */
            if ((v0 <= data[x][y0]) && (data[x][y0] <= v1)) {
                /* line started */
                x0 = x;
                l = 1;
                line = true;
            }
        }
        else {
            /* already on a line */
            if ((v0 <= data[x][y0]) && (data[x][y0] <= v1)) {
                /* still on a line */
                l++;
            }
            else {
                /* line finished */
                line = false;
                if (l > ret_l) {
                    ret_l = l;
                    rx0 = x0;
                }
            }
        }
    }

    if (l > ret_l) {
        ret_l = l;
        rx0 = x0;
    }

    rx1 = rx0 + ret_l;
    return ret_l;
}

int F2::find_maxvline(int x0, float v0, float v1, int &ry0, int &ry1) {
    int ret_l = 0;
    int y0 = -1;
    int l = 0;
    bool line = false;

    for (int y = 0; y < height; y++) {
        
        if (!line) {
            /* no current line */
            if ((v0 <= data[x0][y]) && (data[x0][y] <= v1)) {
                /* line started */
                y0 = y;
                l = 1;
                line = true;
            }
        }
        else {
            /* already on a line */
            if ((v0 <= data[x0][y]) && (data[x0][y] <= v1)) {
                /* still on a line */
                l++;
            }
            else {
                /* line finished */
                line = false;
                if (l > ret_l) {
                    ret_l = l;
                    ry0 = y0;
                }
            }
        }
    }

    if (l > ret_l) {
        ret_l = l;
        ry0 = y0;
    }

    ry1 = ry0 + ret_l;
    return ret_l;
}

void F2::find_clearing(float v0, float v1, int &rx, int &ry) {
    int x, y;
    int x0, x1, y0, y1;
    int sh, sv;
    int rx0, rx1, ry0, ry1;
    int maxs = -1;

    for (x = 0; x < width; x++) {
        y0 = -1;
        y1 = -1;
        sv = find_maxvline(x, v0, v1, y0, y1);
        if (sv <= 0) continue;
        
        sh = 0;
        for (y = y0; y <= y1; y++) {
            x0 = -1;
            x1 = -1;
            sh = find_maxhline(y, v0, v1, x0, x1);
            if (sh <= 0) continue;
            if ((x0 <= x) && (x <= x1)) {
                int s = sh * sv;
                if (s > maxs) {
                    rx0 = x0;
                    rx1 = x1;
                    ry0 = y0;
                    ry1 = y1;
                    maxs = s;
                }
            }
        }
    }


    if (maxs > 0) {
        rx = rx0 + (rx1-rx0)/2;
        ry = ry0 + (ry1-ry0)/2;
    }
}


/**********************************************************************************/


void perlin_noise(F2 &src, F2 &data, int ofsx, int ofsy, int N, float P) {
    unsigned int x, y;
    unsigned int w = data.width;
    unsigned int h = data.height;
    ofsx = abs(ofsx);
    ofsy = abs(ofsy);

    int i;
    float a, f;

    for (i = -1; i < (N-1); i++) {
        f = powf(2, i);
        a = powf(P, i);
        
        for (x = 0; x < w; x++) {
            for (y = 0; y < h; y++) {
                data.data[x][y] += noise( (ofsx + x)*f/w, (ofsy + y)*f/h, src) * a;
            }
        }    
        
    }
}

float noise(float x, float y, F2 &src) {
    // map x,y into source matrix, get low and high index
    int sxl = (int)(x * src.width) % src.width;
    int syl = (int)(y * src.height) % src.height;
    int sxh = (sxl + 1) % src.width;
    int syh = (syl + 1) % src.height;
    
    // interpolate x values for lower and upper y from the source matrix
    float v1 = interpolate( src.data[sxl][syl], src.data[sxh][syl], x*src.width - sxl );
    float v2 = interpolate( src.data[sxl][syh], src.data[sxh][syh], x*src.width - sxl );
    // interpolate the 2 above values
    return interpolate( v1, v2, y*src.height-syl );
}

// cosine interpolation: 
// returns y = f(x)
// f(0) = y1; f(1) = y2; 
float interpolate(float y1, float y2, float x) {
    float a = ( 1.0f - cos(x * M_PI) ) / 2.0f;
    
    return ( y1 * (1.0f - a) + y2 * a );
}


/**********************************************************************************/

void divs(F2 &data, int x0, int y0, int x1, int y1, int dr) {
    int x, y;
    int xm = x0 + (x1-x0) / 2;
    int ym = y0 + (y1-y0) / 2;

    float p00 = data.data[x0][y0];
    float p01 = data.data[x0][y1];
    float p10 = data.data[x1][y0];
    float p11 = data.data[x1][y1];

    //data.data[xm][ym] = (p00 + p01 + p10 + p11) / 4.0f + (rand() % dr);
    data.data[xm][y0] = (p00 + p10) / 2.0f + (rand() % dr) - dr/2;
    data.data[xm][y1] = (p01 + p11) / 2.0f + (rand() % dr) - dr/2;
    data.data[x0][ym] = (p00 + p01) / 2.0f + (rand() % dr) - dr/2;
    data.data[x1][ym] = (p10 + p11) / 2.0f + (rand() % dr) - dr/2;

    float a = (data.data[xm][y0] + data.data[xm][y1]) / 2.0f;
    float b = (data.data[x0][ym] + data.data[x1][ym]) / 2.0f;
    data.data[xm][ym] = (a + b) / 2.0f + (rand() % dr) - dr/2;

    if ( ((x1-x0) <= 3) || ( (y1-y0) <= 3) ) return;
    dr *= 0.7f;

    divs(data, x0, y0, xm, ym, dr);
    divs(data, xm, y0, x1, ym, dr);
    divs(data, x0, ym, xm, y1, dr);
    divs(data, xm, ym, x1, y1, dr);
}

/**********************************************************************************/

void automata(F2 &data, int N) {
    int i, x, y;
    int H = 0;

    x = rand() % data.width;
    y = rand() % data.height;
    for (i = 0; i < N; i++) {

        H -= 1;
        if (H<=0) {
            H = 500;
        }

        data.data[x][y] +=  H;
        switch (rand() % 4) {
        case 0: x++; break;
        case 1: x--; break;
        case 2: y++; break;
        case 3: y--; break;
        }

        if (x < 0) x = data.width-1;
        if (x >= data.width) x = 0;
        if (y < 0) y = data.height-1;
        if (y >= data.width) y = 0; 
    }
}

/**********************************************************************************/

void blob(F2 &data, int R, int span, float v, int res, int x0, int y0) {
    int i;
    float x, y, a, r;
    float maxr = 0;

    if (x0 < 0) x0 = data.width / 2;
    if (y0 < 0) y0 = data.height / 2;

    float blobr[res];
    for (i = 0; i < res; i++) {
        blobr[i] = R + (rand() % span);
        if (blobr[i] > maxr) maxr = blobr[i];
    }
   

    for (x = -maxr; x <= maxr; x++) {
        for (y = -maxr; y <= maxr; y++) { 
            cart2pol(x, y, r, a);

            int i0 = (int)(roundf(a * res / (2*M_PI))) % res;
            int i1 = (i0 + 1) % res;
            float r0 = blobr[i0];
            float r1 = blobr[i1];
            float rx = (r0 + r1) / 2.0f;
            
            if (r <= rx) {
                data.data[x0+(int)x][y0+(int)y] = v;
            }
        }
    }
}


/**********************************************************************************/


/**********************************************************************************/
